/* * MoneyBalance - Android-based calculator for tracking and balancing expenses * Copyright (C) 2012 Ingo van Lil <inguin@gmx.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ivl.android.moneybalance; import ivl.android.moneybalance.dao.CalculationDataSource; import ivl.android.moneybalance.dao.DataBaseHelper; import ivl.android.moneybalance.dao.ExpenseDataSource; import ivl.android.moneybalance.data.Currency; import ivl.android.moneybalance.data.Calculation; import ivl.android.moneybalance.data.Expense; import ivl.android.moneybalance.data.Person; import java.text.DateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v4.view.WindowCompat; import android.support.v7.app.ActionBarActivity; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.ExpandableListView.ExpandableListContextMenuInfo; import android.widget.ExpandableListView.OnChildClickListener; import android.widget.ImageView; import android.widget.TextView; public class ExpenseListActivity extends ActionBarActivity implements OnChildClickListener { public static final String PARAM_CALCULATION_ID = "calculationId"; private long calculationId; private final DataBaseHelper dbHelper = new DataBaseHelper(this); private final CalculationDataSource calculationDataSource = new CalculationDataSource(dbHelper); private ExpenseDataSource expenseDataSource; private static final int ITEM_DELETE = 0; private ExpenseAdapter adapter; private class ExpenseAdapter extends BaseExpandableListAdapter { private boolean groupByPerson = true; private Calculation calculation; private final Map<Person, List<Expense>> expensesByPerson = new HashMap<>(); private final Set<Calendar> dates = new TreeSet<>(); private final Map<Calendar, List<Expense>> expensesByDate = new HashMap<>(); private final LayoutInflater inflater; private final String groupSummaryFormat = getResources().getString(R.string.expenses_summary_format); public ExpenseAdapter(Context context) { inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public void setCalculation(Calculation calculation) { this.calculation = calculation; expenseDataSource = new ExpenseDataSource(dbHelper, calculation); expensesByPerson.clear(); dates.clear(); expensesByDate.clear(); for (Person person : calculation.getPersons()) expensesByPerson.put(person, new ArrayList<Expense>()); for (Expense expense : calculation.getExpenses()) { List<Expense> byPersonList = expensesByPerson.get(expense.getPerson()); byPersonList.add(expense); Calendar date = expense.getDate(); List<Expense> byDateList = expensesByDate.get(date); if (byDateList == null) { byDateList = new ArrayList<>(); expensesByDate.put(date, byDateList); dates.add(date); } byDateList.add(expense); } notifyDataSetChanged(); } public void setGroupByPerson(boolean groupByPerson) { this.groupByPerson = groupByPerson; notifyDataSetChanged(); } @Override public boolean hasStableIds() { return true; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } @Override public int getGroupCount() { if (calculation == null) return 0; else if (groupByPerson) return calculation.getPersons().size(); else return dates != null ? dates.size() : 0; } @Override public long getGroupId(int groupPosition) { if (groupByPerson) { Person person = (Person) getGroup(groupPosition); return person.getId(); } else { Calendar date = (Calendar) getGroup(groupPosition); return date.getTimeInMillis(); } } @Override public Object getGroup(int groupPosition) { if (groupByPerson) { return calculation.getPersons().get(groupPosition); } else { return dates.toArray()[groupPosition]; } } private class GroupViewHolder { public TextView nameView; public TextView summaryView; public ImageView addButton; } @Override public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { View view = convertView; GroupViewHolder holder; if (view == null) { view = inflater.inflate(R.layout.expense_list_group_row, parent, false); holder = new GroupViewHolder(); holder.nameView = (TextView) view.findViewById(android.R.id.text1); holder.summaryView = (TextView) view.findViewById(android.R.id.text2); holder.addButton = (ImageView) view.findViewById(R.id.add_button); view.setTag(holder); } else { holder = (GroupViewHolder) view.getTag(); } List<Expense> expenses; if (groupByPerson) { Person person = (Person) getGroup(groupPosition); holder.nameView.setText(person.getName()); expenses = expensesByPerson.get(person); } else { Calendar date = (Calendar) getGroup(groupPosition); DateFormat format = DateFormat.getDateInstance(); holder.nameView.setText(format.format(date.getTime())); expenses = expensesByDate.get(date); } int count = 0; double total = 0; for (Expense expense : expenses) { count++; total += expense.getExchangedAmount(); } if (count == 0) { holder.summaryView.setText(R.string.no_expenses); } else { String totalStr = calculation.getMainCurrency().getCurrencyHelper().format(total); String summary = String.format(groupSummaryFormat, count, totalStr); holder.summaryView.setText(summary); } holder.addButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (groupByPerson) { Person person = (Person) getGroup(groupPosition); addExpenseForPerson(person.getId()); } else { Calendar date = (Calendar) getGroup(groupPosition); addExpenseForDate(date); } } }); return view; } @Override public int getChildrenCount(int groupPosition) { if (groupByPerson) { Person person = (Person) getGroup(groupPosition); List<Expense> list = expensesByPerson.get(person); return list.size(); } else { Calendar date = (Calendar) getGroup(groupPosition); List<Expense> list = expensesByDate.get(date); return list.size(); } } @Override public long getChildId(int groupPosition, int childPosition) { Expense expense = (Expense) getChild(groupPosition, childPosition); return expense.getId(); } @Override public Object getChild(int groupPosition, int childPosition) { if (groupByPerson) { Person person = (Person) getGroup(groupPosition); List<Expense> list = expensesByPerson.get(person); return list.get(childPosition); } else { Calendar date = (Calendar) getGroup(groupPosition); List<Expense> list = expensesByDate.get(date); return list.get(childPosition); } } private class ChildViewHolder { public TextView titleView; public TextView amountView; public TextView exchangedView; public TextView details1View; public TextView details2View; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { View view = convertView; ChildViewHolder holder; if (view == null) { view = inflater.inflate(R.layout.expense_row, parent, false); holder = new ChildViewHolder(); holder.titleView = (TextView) view.findViewById(R.id.expense_title); holder.amountView = (TextView) view.findViewById(R.id.expense_amount); holder.exchangedView = (TextView) view.findViewById(R.id.expense_exchanged_amount); holder.details1View = (TextView) view.findViewById(R.id.expense_details_1); holder.details2View = (TextView) view.findViewById(R.id.expense_details_2); view.setTag(holder); } else { holder = (ChildViewHolder) view.getTag(); } Expense expense = (Expense) getChild(groupPosition, childPosition); Currency currency = expense.getCurrency(); CurrencyHelper currencyHelper = currency.getCurrencyHelper(); holder.titleView.setText(expense.getTitle()); holder.amountView.setText(currencyHelper.format(expense.getAmount())); if (expense.getCurrency().equals(calculation.getMainCurrency())) { holder.exchangedView.setVisibility(View.GONE); } else { CurrencyHelper mainCurrencyHelper = calculation.getMainCurrency().getCurrencyHelper(); double exchanged = expense.getExchangedAmount(); holder.exchangedView.setVisibility(View.VISIBLE); holder.exchangedView.setText(mainCurrencyHelper.format(exchanged)); } if (groupByPerson) { DateFormat format = DateFormat.getDateInstance(); holder.details1View.setText(format.format(expense.getDate().getTime())); } else { Person person = expense.getPerson(); holder.details1View.setText(person.getName()); } if (expense.isUnevenSplit()) { List<Person> persons = calculation.getPersons(); List<Double> shares = expense.getShares(persons); StringBuilder msg = new StringBuilder(); for (int i = 0; i < persons.size(); i++) { if (shares.get(i) > 0) { Person person = persons.get(i); if (msg.length() > 0) msg.append("; "); String shareStr = currencyHelper.format(shares.get(i)); msg.append(String.format("%s: %s", person.getName(), shareStr)); } } holder.details2View.setVisibility(View.VISIBLE); holder.details2View.setText(msg); } else { holder.details2View.setVisibility(View.GONE); } return view; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR); setContentView(R.layout.expense_list); Intent intent = getIntent(); calculationId = intent.getLongExtra(PARAM_CALCULATION_ID, -1); ExpandableListView listView = (ExpandableListView) findViewById(R.id.expense_list); adapter = new ExpenseAdapter(this); listView.setAdapter(adapter); listView.setOnChildClickListener(this); registerForContextMenu(listView); setContentView(listView); refresh(); } private void refresh() { Calculation calculation = calculationDataSource.get(calculationId); setTitle(calculation.getTitle()); adapter.setCalculation(calculation); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.expense_list_options, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { Intent intent; switch (item.getItemId()) { case R.id.new_expense: addExpense(); case R.id.group_by_person: adapter.setGroupByPerson(true); return true; case R.id.group_by_date: adapter.setGroupByPerson(false); return true; case R.id.manage_currencies: intent = new Intent(this, ManageCurrenciesActivity.class); intent.putExtra(ManageCurrenciesActivity.PARAM_CALCULATION_ID, calculationId); startActivity(intent); return true; case R.id.calcluation_summary: intent = new Intent(this, SummaryActivity.class); intent.putExtra(SummaryActivity.PARAM_CALCULATION_ID, calculationId); startActivity(intent); return true; case R.id.export_calculation: CsvExporter.export(calculationDataSource.get(calculationId), this); return true; default: return super.onOptionsItemSelected(item); } } @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { Expense expense = (Expense) adapter.getChild(groupPosition, childPosition); Intent intent = new Intent(this, ExpenseEditorActivity.class); intent.putExtra(ExpenseEditorActivity.PARAM_CALCULATION_ID, expense.getCalculation().getId()); intent.putExtra(ExpenseEditorActivity.PARAM_EXPENSE_ID, expense.getId()); startActivity(intent); return true; } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if (v.getId() == R.id.expense_list) { ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo; int group = ExpandableListView.getPackedPositionGroup(info.packedPosition); int child = ExpandableListView.getPackedPositionChild(info.packedPosition); if (child != -1) { Expense expense = (Expense) adapter.getChild(group, child); menu.setHeaderTitle(expense.getTitle()); menu.add(0, ITEM_DELETE, 0, R.string.menu_delete); } } } @Override public boolean onContextItemSelected(MenuItem item) { if (item.getItemId() == ITEM_DELETE) { ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) item.getMenuInfo(); int group = ExpandableListView.getPackedPositionGroup(info.packedPosition); int child = ExpandableListView.getPackedPositionChild(info.packedPosition); if (child != -1) { Expense expense = (Expense) adapter.getChild(group, child); expenseDataSource.delete(expense.getId()); refresh(); } } else { return false; } return true; } @Override protected void onPause() { super.onPause(); dbHelper.close(); } @Override protected void onResume() { refresh(); super.onResume(); } private void addExpenseForPerson(long personId) { Intent intent = new Intent(this, ExpenseEditorActivity.class); intent.putExtra(ExpenseEditorActivity.PARAM_CALCULATION_ID, calculationId); intent.putExtra(ExpenseEditorActivity.PARAM_PERSON_ID, personId); startActivity(intent); } private void addExpenseForDate(Calendar date) { Intent intent = new Intent(this, ExpenseEditorActivity.class); intent.putExtra(ExpenseEditorActivity.PARAM_CALCULATION_ID, calculationId); intent.putExtra(ExpenseEditorActivity.PARAM_DATE, date.getTimeInMillis()); startActivity(intent); } private void addExpense() { Intent intent = new Intent(this, ExpenseEditorActivity.class); intent.putExtra(ExpenseEditorActivity.PARAM_CALCULATION_ID, calculationId); startActivity(intent); } }